package il.ac.bgu.soa2009.hw6.cinema.impl;

import il.ac.bgu.soa2009.hw6.cinema.Movie;
import il.ac.bgu.soa2009.hw6.cinema.Ticket;
import il.ac.bgu.soa2009.hw6.cinema.TicketRequest;
import il.ac.bgu.soa2009.hw6.cinema.TicketIdentifier;

import java.util.*;

/**
 * User: Henry
 * Date: 03/04/2009
 * Time: 19:49:07
 */
@SuppressWarnings({"deprecation"})
public class HallContainer {
    private int reservationCounter=0;
    private Movie movie;
    private int seatCount;
    private Map<Date, Map<Integer, List<Integer>>> orders;
    private Map<Integer,Date> ordersById;
    private int hallNumber;

    public HallContainer(int hallNumber) {
        seatCount = -1;
        this.hallNumber = hallNumber;
    }

    public void setMovie(Movie movie) {
        this.movie = movie;
        if (isLegalHall()) {
            orders = new LinkedHashMap<Date, Map<Integer, List<Integer>>>();
            ordersById = new LinkedHashMap<Integer, Date>();
        }
    }

    public void setSeatsCount(int seats) {
        this.seatCount = seats;
        if (isLegalHall()) {
            orders = new LinkedHashMap<Date, Map<Integer, List<Integer>>>();
            ordersById = new LinkedHashMap<Integer, Date>();
        }
    }

     public Ticket orderTicket(TicketRequest order) throws CinemaException {
        synchronized (HallContainer.class) {
            int count = order.getTicketCount();
            if (count < 1) {
                throw new CinemaException("Number of tickets must be greater then 0!");
            }
            Date orderDate = normalDate(order.getDate());
            if (count > seatCount - getAlreadyTakenSeatsCount(orderDate)) {
                throw new NotEnoughSeatsAvaliableException("There are no seats available for movie: "+movie.getMovieName());
            }
            boolean[] takenSeats = new boolean[seatCount];
            for (int i = 0; i < seatCount; i++) {
                takenSeats[i] = false;
            }
            Map<Integer, List<Integer>> ordersForDate = orders.get(orderDate);
            if (ordersForDate == null) {
                ordersForDate = new LinkedHashMap<Integer, List<Integer>>();
                orders.put(orderDate,ordersForDate) ;
            }
            Collection<List<Integer>> seatsCollection = ordersForDate.values();
            updateTakenSeatsArray(takenSeats, seatsCollection);
            List<Integer> resSeats = getReservableSeats(count, takenSeats);
            int[] reservedSeats = new int[count];
            for (int i=0;i<count;i++) {
                reservedSeats[i]=resSeats.get(i);
            }
            int reservationNum = getNextReservationNumber();
            ordersForDate.put(reservationNum,resSeats);
            ordersById.put(reservationNum,orderDate);
            TicketIdentifier identifier = new TicketIdentifier(reservationNum, hallNumber);
            return new Ticket(identifier,reservedSeats);
        }
    }


    public boolean isScreening() {
        return isLegalHall();
    }

    public void cancelTicket(int orderId) throws TicketDoesNotExistsException {
        Date date = ordersById.get(orderId);
        if (date == null) {
            throw new TicketDoesNotExistsException("Order id does not exists: "+orderId);
        }
        orders.get(date).remove(orderId);
        ordersById.remove(orderId);
    }

    public Movie getMovie() {
        return movie;
    }

    private Date normalDate(Date date) {
        date.setHours(0);
        date.setMinutes(0);
        date.setSeconds(0);
        return date;
    }

    private int getAlreadyTakenSeatsCount(Date date) {
        int res = 0;
        Map<Integer, List<Integer>> ordersForDate = this.orders.get(normalDate(date));
        if (ordersForDate!=null) {
            Collection<List<Integer>> seatsCollection = ordersForDate.values();
            for (List<Integer> seatList : seatsCollection) {
                res += seatList.size();
            }
        }
        return res;
    }

    private boolean isLegalHall() {
        return movie != null && seatCount > 0;
    }


    private List<Integer> getReservableSeats(int count, boolean[] takenSeats) {
        List<Integer> resSeats = new LinkedList<Integer>();
        while (resSeats.size() < count) {
            resSeats.add(takeNextAvailableSeat(takenSeats));
        }
        return resSeats;
    }

    private void updateTakenSeatsArray(boolean[] takenSeats, Collection<List<Integer>> seatsCollection) {
        for (List<Integer> seatList : seatsCollection) {
            for (Integer seatNumber : seatList) {
                takenSeats[seatNumber] = true;
            }
        }
    }

    private int getNextReservationNumber(){
        int res = reservationCounter;
        reservationCounter++;
        return res;
    }

    private Integer takeNextAvailableSeat(boolean[] takenSeats) {
        int res = 0;
        while (takenSeats[res] && res < seatCount) {
            res++;
        }
        if (res == seatCount) {
            throw new RuntimeException("res==seatCount which should not happen!");
        }
        takenSeats[res] = true;
        return res;
    }

    public int getNumber() {
        return hallNumber;
    }
}
